﻿(function () {
	var tokenRegex = /\{([^\}]+)\}/g,
	objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
	replacer = function (all, key, obj) {
		var res = obj;
		key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
			name = name || quotedName;
			if (res) {
				if (name in res) {
					res = res[name];
				}
				typeof res == "function" && isFunc && (res = res());
			}
		});
		res = (res == null || res == obj ? all : res) + "";
		return res;
	},
	fill = function (str, obj) {
		return String(str).replace(tokenRegex, function (all, key) {
			return replacer(all, key, obj);
		});
	};
	Raphael.fn.popup = function (X, Y, set, pos, ret) {
		pos = String(pos || "top-middle").split("-");
		pos[1] = pos[1] || "middle";
		var r = 5,
			bb = set.getBBox(),
			w = Math.round(bb.width),
			h = Math.round(bb.height),
			x = Math.round(bb.x) - r,
			y = Math.round(bb.y) - r,
			gap = Math.min(h / 4, w / 4, 6),
			shapes = {
				top: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}l-{right},0-{gap},{gap}-{gap}-{gap}-{left},0a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z",
				bottom: "M{x},{y}l{left},0,{gap}-{gap},{gap},{gap},{right},0a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z",
				right: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}l0-{bottom}-{gap}-{gap},{gap}-{gap},0-{top}a{r},{r},0,0,1,{r}-{r}z",
				left: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}l0,{top},{gap},{gap}-{gap},{gap},0,{bottom}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z"
			},
			offset = {
				hx0: X - (x + r + w - gap * 2),
				hx1: X - (x + r + w / 2 - gap),
				hx2: X - (x + r + gap),
				vhy: Y - (y + r + h + r + gap),
				"^hy": Y - (y - gap)

			},
			mask = [{
				x: x + r,
				y: y,
				w: w,
				w4: w / 4,
				h4: h / 4,
				right: 0,
				left: w - gap * 2,
				bottom: 0,
				top: h - gap * 2,
				r: r,
				h: h,
				gap: gap
			}, {
				x: x + r,
				y: y,
				w: w,
				w4: w / 4,
				h4: h / 4,
				left: w / 2 - gap,
				right: w / 2 - gap,
				top: h / 2 - gap,
				bottom: h / 2 - gap,
				r: r,
				h: h,
				gap: gap
			}, {
				x: x + r,
				y: y,
				w: w,
				w4: w / 4,
				h4: h / 4,
				left: 0,
				right: w - gap * 2,
				top: 0,
				bottom: h - gap * 2,
				r: r,
				h: h,
				gap: gap
			}][pos[1] == "middle" ? 1 : (pos[1] == "top" || pos[1] == "left") * 2];
		var dx = 0,
				dy = 0,
				out = this.path(fill(shapes[pos[0]], mask)).insertBefore(set);
		switch (pos[0]) {
			case "top":
				dx = X - (x + r + mask.left + gap);
				dy = Y - (y + r + h + r + gap);
				break;
			case "bottom":
				dx = X - (x + r + mask.left + gap);
				dy = Y - (y - gap);
				break;
			case "left":
				dx = X - (x + r + w + r + gap);
				dy = Y - (y + r + mask.top + gap);
				break;
			case "right":
				dx = X - (x - gap);
				dy = Y - (y + r + mask.top + gap);
				break;
		}
		out.translate(dx, dy);
		if (ret) {
			ret = out.attr("path");
			out.remove();
			return {
				path: ret,
				dx: dx,
				dy: dy
			};
		}
		set.translate(dx, dy);
		return out;
	};
})();